package be.sc.classicnetwork.dao.hibernate;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.support.DataAccessUtils;

import be.sc.classicnetwork.dao.CarDao;
import be.sc.classicnetwork.dao.PaginationSearchResult;
import be.sc.classicnetwork.model.Car;
import be.sc.classicnetwork.model.Contact;
import be.sc.classicnetwork.model.Image;
import be.sc.classicnetwork.model.Make;

/**
 * CarDaoImpl.
 */
public class CarDaoImpl extends HibernateGenericDao<Car> implements CarDao {

	private static final Order DEFAULT_ORDER = Order.asc("mk.name");

	private static final Map<String, String> ALIASES;

	static {
		ALIASES = new HashMap<String, String>();
		ALIASES.put("make", "mk");
	}

	public CarDaoImpl() {
		super();
	}

	public CarDaoImpl(Class<Car> clazz) {
		super(clazz);
	}

	/**
	 * @see be.sc.classicnetwork.dao.CarDao#findAll(int, int, boolean)
	 */
	public PaginationSearchResult findAll(int first, int max,
			boolean enableFilterPublished) {
		PaginationSearchResult result = new PaginationSearchResult();
		if (enableFilterPublished) {
			getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
		}
		result.setList(this.findByCriteria(first, max, ALIASES, null,
				new Order[] { DEFAULT_ORDER }));
		result.setCount(countAll());
		return result;
	}

	/**
	 * @see be.sc.classicnetwork.dao.hibernate.HibernateGenericDao#findByIdFull(int)
	 */
	@Override
	public Car findByIdFull(String id) {
		Car car = super.findByIdFull(id);
		if (car != null) {
			getHibernateTemplate().initialize(car.getOptions());
		}
		return car;
	}

	/**
	 * @see be.sc.classicnetwork.dao.CarDao#countMakes(boolean)
	 */
	@SuppressWarnings("unchecked")
	public Collection<Object[]> countMakes(boolean enableFilterPublished) {
		if (enableFilterPublished) {
			getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
		}
		return getHibernateTemplate()
				.find(
						"select  car.make.name, count(*), car.make.id from Car car group by car.make.name, car.make.id order by car.make.name");
	}

	/**
	 * @see be.sc.classicnetwork.dao.CarDao#findByMake(int, int, boolean,
	 *      be.sc.classicnetwork.model.Make)
	 */
	public PaginationSearchResult findByMake(int first, int max,
			boolean enableFilterPublished, Make make) {
		PaginationSearchResult result = new PaginationSearchResult();
		if (enableFilterPublished) {
			getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
		}
		Criterion criterion = Restrictions.eq("make", make);
		result.setList(findByCriteria(first, max, ALIASES,
				new Criterion[] { criterion }, new Order[] { DEFAULT_ORDER }));
		result.setCount(countByCriteria(null, criterion));
		return result;
	}

	/**
	 * Used by findByModelOrMakeLike here under.
	 */
	private DetachedCriteria getFindByModelOrMakeLikeCriteria(String model) {
		// split the query string and make a criterion of each token on
		// make or model
		String[] tokens = model.split(" ");

		// set the criteria
		DetachedCriteria criteria = DetachedCriteria.forClass(Car.class);
		criteria.createAlias("make", "mk");
		for (String s : tokens) {
			criteria.add(Restrictions.or(Restrictions.ilike("model", s,
					MatchMode.ANYWHERE), Restrictions.ilike("mk.name", s,
					MatchMode.ANYWHERE)));
		}

		return criteria;
	}

	/**
	 * @see be.sc.classicnetwork.dao.CarDao#findByModelOrMakeLike(int, int,
	 *      boolean, String)
	 */
	@SuppressWarnings("unchecked")
	public PaginationSearchResult findByModelOrMakeLike(int first, int max,
			boolean enableFilterPublished, String model) {
		PaginationSearchResult result = new PaginationSearchResult();
		DetachedCriteria criteria;

		if (enableFilterPublished) {
			getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
		}

		// count tuples
		criteria = getFindByModelOrMakeLikeCriteria(model);
		criteria.setProjection(Projections.rowCount());
		result.setCount(DataAccessUtils.intResult(getHibernateTemplate()
				.findByCriteria(criteria)));

		// get tuples
		criteria = getFindByModelOrMakeLikeCriteria(model);
		criteria.addOrder(DEFAULT_ORDER);
		result.setList(getHibernateTemplate().findByCriteria(criteria));

		return result;
	}

	/**
	 * @see be.sc.classicnetwork.dao.CarDao#findImageById(int)
	 */
	public Image findImageById(String id) {
		return (Image) getHibernateTemplate().get(Image.class, id);
	}

	/**
	 * @see be.sc.classicnetwork.dao.CarDao#findContactsForNotification(int)
	 */
	@SuppressWarnings("unchecked")
	public Collection<Contact> findContactsForNotification(String carId) {
		return getHibernateTemplate()
				.find(
						"select contact from Contact contact, Car car "
								+ "where car.id=? and car.make in elements(contact.makes) "
								+ "and (car.site is null or car.site not in elements(contact.sites)) "
								+ "and contact.published = true", carId);
	}

}
